/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package bio;

import java.util.ArrayList;
import java.util.BitSet;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Random;
import helper.BagWithNumbers;
import helper.Config;
import schedulingScheme.CoverageMatrix;
import schedulingScheme.GeneticOperations;
import schedulingScheme.POI;
import schedulingScheme.SensorNode;
import schedulingScheme.SensorsController;

/**
 *
 * @author Andrzej
 */
public class Bio implements SensorsController {

    private Random random = Config.getSalt();
    private int B;
    private int T;
    private List<SensorNode> nodes;
    private List<POI> pois;
    int populationSize = 100;
    double crossoverRate = 0.6;
    double mutationRate = 0.004;
    private FitnessValue fv;
    private Chromosome bestSolution;

    public Bio(List<SensorNode> nodes, List<POI> pois, int B, int T) {
        this.B = B;
        this.T = T;
        this.nodes = nodes;
        this.pois = pois;
        fv = new FitnessValue(new CoverageMatrix(nodes, pois));
    }

    public List<SensorNode> replaceDiedSensor(SensorNode s) {
        return Collections.EMPTY_LIST;
    }

    public boolean reactToTimePassed(int time) {
        return false;
    }

    public List<SensorNode> initializeNodes() {
        List<Chromosome> population = new ArrayList<Chromosome>(populationSize);
        for (int i = 0; i < populationSize; i++) {
            population.add(produceRandomChromosome2());
        }


        bestSolution = population.get(0);
//        Collections.sort(population, Collections.reverseOrder(fv));
//        population = population.subList(0, populationSize);
        int bestSolutionUnchanged = 0;

        for (int k = 0; k < 500 && bestSolutionUnchanged < 100; k++) {
            Collections.sort(population, fv);
            System.out.println("fitness value:" + fv.fitnessValue(population.get(population.size() -1)));

            if (fv.fitnessValue(population.get(population.size() - 1)) > fv.fitnessValue(bestSolution)) {
                bestSolution = population.get(population.size() -1).clone();
                bestSolutionUnchanged = 0;
//                System.out.println("" + bestSolution);
            } else {
                bestSolutionUnchanged++;
            }
            List<Chromosome> newPopulation = new ArrayList<Chromosome>(populationSize);

            for (int i = 0; i < populationSize / 2; i++) {

                Chromosome parent1 = GeneticOperations.tournament(population, fv, 10, 0.9);
                Chromosome parent2 = GeneticOperations.tournament(population, fv, 10, 0.9);

                List<Chromosome> newChromosomes;
                if (random.nextDouble() < crossoverRate) {
                    newChromosomes = parent1.singlePointCrossover(parent2);

                } else {
                    newChromosomes = new ArrayList<Chromosome>(2);
                    newChromosomes.add(parent1);
                    newChromosomes.add(parent2);
                }
                for (Chromosome c : newChromosomes) {
                    if (random.nextDouble() < mutationRate) {
                        c.mutatate();
                    }
                }
                newPopulation.addAll(newChromosomes);

            }
            population = newPopulation;


        }



        System.out.println("best solution:" + bestSolution.toString() + " fitness value:" + fv.fitnessValue(bestSolution));

        List<SensorNode> res = new ArrayList<SensorNode>();
        for (int i = 0; i < nodes.size(); i++) {
            if (bestSolution.getAllele(i).get(0)) {
                res.add(nodes.get(i));
            }
        }
        return res;

    }

    private Chromosome produceRandomChromosome() {
        Chromosome c = new Chromosome(nodes.size(), T);
        for (int i = 0; i < nodes.size(); i++) {
            BagWithNumbers bwn = new BagWithNumbers(T);
            BitSet gen = new BitSet(T);
            for (int j = 0; j < B; j++) {
                int drawnNumber = bwn.drawNumber();
                gen.set(drawnNumber);
            }
            c.setAllele(i, gen);
        }
        return c;

    }

        private Chromosome produceRandomChromosome2() {
        Chromosome c = new Chromosome(nodes.size(), T);
        for (int i=0; i < nodes.size(); i++){
            c.setAllele(i, new BitSet(T));
        }
        double p = ((double)B)/T;
        int n = B*nodes.size()/T;
        BagWithNumbers bwn = new BagWithNumbers(nodes.size());
        for (int i=0; i < T - 1; i++){
            
            for (int j =0; j < n; j++){
                int index = bwn.drawNumber();
                c.getAllele(index).set(i);
            }
        }
        for (int i=0; i < nodes.size();i++){
            if (c.getAllele(i).cardinality() == 0)
                c.getAllele(i).set(T-1);
        }
        
        return c;

    }

    public List<SensorNode> roundPassed(int roundNumber) {
        if (roundNumber == 0 || roundNumber % 10 != 0) {
            return null;
        }
        int r = roundNumber / 10;
        List<SensorNode> res = new ArrayList<SensorNode>();
        for (int i = 0; i < nodes.size(); i++) {
            if (bestSolution.getAllele(i).get(r % T)) {
                res.add(nodes.get(i));
            }
        }
        return res;

    }
}

class FitnessValue implements Comparator<Chromosome> {

    private CoverageMatrix cm;

    public FitnessValue(CoverageMatrix cm) {
        this.cm = cm;
    }

    public int fitnessValue(Chromosome chromosome) {
        //just sum POIs in range in all spans
        int res = 0;
        for (int i = 0; i < chromosome.getAllele(0).size(); i++) {
            BitSet tmp = new BitSet(cm.coverageOfSensor(0).size());
            for (int j = 0; j < chromosome.length(); j++) {
                if (chromosome.getAllele(j).get(i)) {
                    tmp.or(cm.coverageOfSensor(j));
                }
            }
            res += tmp.cardinality();
        }
        return res;
    }

    public int compare(Chromosome o1, Chromosome o2) {
        return new Integer(fitnessValue(o1)).compareTo(fitnessValue(o2));
    }
}
